介绍 extends 条件类型用法和 infer 搭配进行类型推断
extends 条件类型
基本语法
extends 条件类型有点类似三元运算符,下面来看一下它的基本用法。
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
// Dog 类型是 Animal 的子类型?true 则返回 number,否者返回 string
type Example1 = Dog extends Animal ? number : string; // number
// RegExp 类型是 Animal 的子类型?true 则返回 number,否者返回 string
type Example2 = RegExp extends Animal ? number : string; // string
important
判断一个对象类型是否为另一个对象类型的子类型,只需要看这个类型是否涵盖了父类型的所有属性。
因此,子类型 的属性数量一定是大于或者等于 父类型 的属性数量的。
条件类型的强大之处在于配合泛型一起使用,下面来看一个案例。
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
if(typeof nameOrId === 'number') return {id: nameOrId};
else return {name: nameOrId};
}
这里我们使用了 TS 中的 函数重载 机制,可以根据传入参数的类型来决定返回值。
let a = createLabel(1); // IdLable
let b = createLabel("1"); // NameLable
let c = createLabel(Math.random() ? "hello" : 42); // IdLable | NameLable
实际上我们可以使用 extends 条件类型来简化函数重载。
// T 如果是 number 类型则返回 IdLabel 类型,否者返回 NameLabel 类型
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel<T extends number | string>(nameOrId: T): NameOrId<T> {
return (typeof nameOrId === "number") ? {id: nameOrId} as NameOrId<T> : {name: nameOrId} as NameOrId<T>;
}
let a = createLabel(1); // IdLable
let b = createLabel("1"); // NameLable
let c = createLabel(Math.random() ? "hello" : 42); // IdLable | NameLable
条件类型约束
先看一个泛型约束的基本用法例子。
// 获取对象类型中 message 属性的类型
type MessageOf<T extends { message: unknown }> = T["message"];
interface Email {
message: string;
}
type EmailMessageContents = MessageOf<Email>;
我们使用 MessageOf 时,要求传入的泛型参数必须带有 message 属性,否者会编译报错。
现在实现一个需求:让 MessageOf 的泛型参数可以为任意类型,具有 message 属性则返回该属性的类型,不具有 message 属性则返回 never 类型。这个需求可以使用条件泛型约束实现。
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
条件类型在联合类型中的使用
上面所有的例子都是对象类型和条件类型搭配使用,还有一个常见的用法就是在联合类型中使用条件类型。
T extends U ? X : Y
这里的 T 和 U 都是联合类型,把联合类型看作是一个集合,整个过程中会把 T 中的每一个子类型拿出来和 U 集合进行比较,判断 T 中的子类型是否为 U 集合的子集,如果是则返回 X,否者返回 Y,最终会把所有返回的结果重新进行联合。
type P<T> = T extends 0 | 1 ? "A" : "B";
type A = P<0 | 1 | 2>; // type A = "A" | "B";
整个过程就是:
- 判断
0是否为0 | 1的子集,返回"A" - 判断
1是否为0 | 1的子集,返回"A" - 判断
2是否为0 | 1的子集 ,返回"B" - 最后将所有返回结果联合起来,
"A" | "A" | "B"即"A" | "B"
important
上面的过程 当且仅当使用泛型时,联合类型的条件判断才会具有一个分发的过程,如果把 T 写为一个固定的联合类型:
type P = 0 | 1 | 2 extends 0 | 1 ? "A" : "B"; // type A = "B"
此时会把 0 | 1 | 2 和 0 | 1 都看作一个整体,直接判断 0 | 1 | 2 是否为 0 | 1 的子集,如果是则返回 "A",否者返回 "B",所以这里的类型 P 实际上是一个字面量类型 "B"。
使用泛型是如果想要避免分发过程,将泛型参数看作一个整体,可以加上 []。
type P<T> = [T] extends 0 | 1 ? "A" : "B";
type A = P<0 | 1 | 2>; // type A = "B";
下面我们来实现几个应用,利用条件类型实现联合类型的交集、差集。
先明确一下概念:
- 交集:
T中的元素能在U集合中找到,所有的这些元素形成的集合就是T与U的交集 - 差集:
T中的元素能在U集合中找到,从T集合中去掉这些元素后就是T差U
type Filter<T, U> = T extends U ? T : never; // 交集
type Diff<T, U> = T extends U ? never : T; // 差集
TS 中也提供了这两种需求的泛型工具类型:Extract 交集 和 Exclude 差集。
使用 infer 在条件类型中推断
基本语法
我们先来实现一个需求,获取传入的数组类型的元素类型。
type Flatten<T> = T extends unknown[] ? T[number] : T;
let num: Flatten<number[]> = 1; // num 是 number 类型
这里我们使用了数组的类型索引机制 T[number],可以返回数组的元素类型。 下面我们通过 infer 来实现这个需求。
type Flatten<T> = T extends Array<infer R> ? R : T;
这里的 infer R 相当于在条件类型中定义了一个新的类型 R,这里可以理解为如果 T 是一个数组类型,那么就会将该数组类型的元素类型赋值给 R,然后返回 R,否者返回 T。
important
T extends U ? X : Y
infer 语句只能在条件类型 extends 的 U 位置处使用,作用是定义一个类型 R。
如果条件满足则会根据类型 U 给 R 赋值,上面的例子中是根据 T = Array<R> 这个表达式,给 R 类型赋值的,比如这里 T 如果是 string[],那么 R 就会被赋值为 string 类型。
还需要记住一点,R 只能在条件判断为 true 的分支中使用。
分析 ReturnType 与 Parameters
TS 官方提供了有关函数类型的两个泛型工具类型:
ReturnType<T>,用于获取一个函数类型的返回值类型Parameters<T>,用于获取一个函数类型的所有参数类型,最后将参数类型作为元组返回
这两个泛型工具类型很典型的使用了 extends 条件类型与 infer 类型推断,下面来分析一下它们的源码。
ReturnType
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
在泛型参数列表中使用 T extends (...args: any[]) => any 约束泛型参数必须是一个函数类型,然后再通过 T extends (...args: any[]) => infer R,将函数类型 T 的返回值类型赋值给 infer R,最后返回 R。
Parameters
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
在泛型参数列表中使用 T extends (...args: any) => any 约束泛型参数必须是一个函数类型,然后再通过 T extends (...args: infer P) => any,利用 函数的剩余参数 将所有参数类型封装到一个元组中并赋值给 infer P,最后返回 P。